// $Id: DbConnection.cs 10 2008-02-29 15:58:02Z mcartoixa $
/*
 *  Copyright (C) 2007 Noury Solutions
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 3 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 */
/*! \file
 * \brief Definition of a database connection adapter.
 *
 * \author $Author: mcartoixa $
 * \date $Date: 2008-02-29 16:58:02 +0100 (ven., 29 févr. 2008) $
 * \version $Revision: 10 $
 */

using System;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Threading;

namespace Salamanca.DataAccess.Data
{


    /// <summary><see cref="IDbConnection" /> wrapper that, when disposed, leaves the <see cref="IDbConnection" /> in the <see cref="IDbConnection.State" /> it found it in.</summary>
    /// <remarks></remarks>
    public sealed class DbConnectionAdapter:
        IDbConnection
    {

        /// <summary></summary>
        public DbConnectionAdapter(IDbConnection connection):
            this(connection, false)
        {
        }

        /// <summary></summary>
        public DbConnectionAdapter(IDbConnection connection, bool clone)
        {
            Debug.Assert(connection!=null);
            if (connection==null)
                throw new ArgumentNullException("connection");

            if (clone)
            {
                ICloneable c=connection as ICloneable;

                Debug.Assert(c!=null);
                if (c==null)
                    throw new ArgumentException("Connection must implement ICloneable", "connection");

                connection=(IDbConnection)c.Clone();
            }

            _ConnectionStateChangedHandler=new StateChangeEventHandler(ConnectionStateChangedHandler);

            _Connection=connection;
            Monitor.Enter(_Connection);

            _ConnectionState=connection.State;
            if (_Connection is DbConnection)
                ((DbConnection)_Connection).StateChange+=_ConnectionStateChangedHandler;
        }

        /// <summary></summary>
        ~DbConnectionAdapter()
        {
            Dispose(false);
        }


        /// <summary></summary>
        /// <returns></returns>
        public IDbTransaction BeginTransaction()
        {
            return new DbTransactionAdapter(_Connection.BeginTransaction() as DbTransaction);
        }

        /// <summary></summary>
        /// <param name="il"></param>
        /// <returns></returns>
        public IDbTransaction BeginTransaction(IsolationLevel il)
        {
            return new DbTransactionAdapter(_Connection.BeginTransaction(il) as DbTransaction);
        }

        /// <summary></summary>
        /// <param name="databaseName"></param>
        public void ChangeDatabase(string databaseName)
        {
            _Connection.ChangeDatabase(databaseName);
        }

        /// <summary></summary>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Close()
        {
            if (_Connection is DbConnection)
                ((DbConnection)_Connection).StateChange-=_ConnectionStateChangedHandler;
            try
            {
                if (_Connection.State!=ConnectionState.Closed)
                {
#if (!NET_CF)
                    Traces.TraceDatabaseActionIf(Configuration.Switch, TraceLevel.Info, _Connection, "Close");
#endif
                    _Connection.Close();
                }
            } catch
            {
                if (_Connection is DbConnection)
                    ((DbConnection)_Connection).StateChange+=_ConnectionStateChangedHandler;

                throw;
            }
        }

        /// <summary></summary>
        /// <returns></returns>
        public IDbCommand CreateCommand()
        {
            return _Connection.CreateCommand();
        }

        /// <summary></summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

#if (!NET_CF)
        /// <summary></summary>
        public void EnlistTransaction(Transaction transaction)
        {
            if (transaction!=null)
            {
                DbConnection c=_Connection as DbConnection;
                if (c!=null)
                {
                    System.Transactions.Transaction t=(System.Transactions.Transaction)transaction;
                    if (t!=null)
                        c.EnlistTransaction(t);
                }
            }
        }
#endif

        /// <summary></summary>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Open()
        {
            if (_Connection is DbConnection)
                ((DbConnection)_Connection).StateChange-=_ConnectionStateChangedHandler;
            try
            {
                if (_Connection.State!=ConnectionState.Open)
                {
#if (!NET_CF)
                    Traces.TraceDatabaseActionIf(Configuration.Switch, TraceLevel.Info, _Connection, "Open");
#endif
                    _Connection.Open();
                }
            } catch
            {
                if (_Connection is DbConnection)
                    ((DbConnection)_Connection).StateChange+=_ConnectionStateChangedHandler;

                throw;
            }
        }

        IDbTransaction IDbConnection.BeginTransaction()
        {
            return BeginTransaction();
        }

        IDbTransaction IDbConnection.BeginTransaction(IsolationLevel il)
        {
            return BeginTransaction(il);
        }

        /// <summary></summary>
        private void Dispose(bool disposing)
        {
            if (_Disposed)
                return;

            // Back to initial state
            if (_Connection is DbConnection)
                ((DbConnection)_Connection).StateChange-=_ConnectionStateChangedHandler;
            ResetConnectionState(_ConnectionState);

            Monitor.Exit(_Connection);
            _Disposed=true;
        }

        private void ResetConnectionState(ConnectionState state)
        {
            if (_Connection.State!=state)
                switch (state)
                {
                case ConnectionState.Closed:
                    Close();
                    break;
                case ConnectionState.Open:
                    Open();
                    break;
                }
        }

        private void ConnectionStateChangedHandler(object sender, StateChangeEventArgs e)
        {
            // Who allowed you to change the connection state ?
#if (!NET_CF)
            Traces.TraceDatabaseActionIf(Configuration.Switch, TraceLevel.Warning, _Connection, "Connection state changed");
#endif
            ResetConnectionState(e.OriginalState);
        }

        /// <summary>Gets or sets the string used to open a database.</summary>
        /// <remarks>The <see cref="ConnectionString" /> property can be set only while the connection is closed.</remarks>
        public string ConnectionString
        {
            get
            {
                return _Connection.ConnectionString;
            }
            set
            {
                _Connection.ConnectionString=value;
            }
        }

        /// <summary>Gets the time to wait while establishing a connection before terminating the attempt and generating an error.</summary>
        /// <value>The time (in seconds) to wait for a connection to open. The default value is determined by the specific type of connection that you are using.</value>
        public int ConnectionTimeout
        {
            get
            {
                return _Connection.ConnectionTimeout;
            }
        }

        /// <summary>Gets the name of the current database after a connection is opened, or the database name specified in the connection string before the connection is opened.</summary>
        /// <value>The name of the current database or the name of the database to be used after a connection is opened. The default value is an empty string.</value>
        /// <remarks>A connection typically updates this property dynamically when the current database changes.</remarks>
        public string Database
        {
            get
            {
                return _Connection.Database;
            }
        }

        /// <summary>Gets the state of the connection.</summary>
        /// <value>The state of the connection.</value>
        public ConnectionState State
        {
            get
            {
                return _Connection.State;
            }
        }

        internal IDbConnection Connection
        {
            get
            {
                if (_Disposed)
                    return null;

                return _Connection;
            }
        }

        /// <summary></summary>
        private bool _Disposed;
        /// <summary></summary>
        private IDbConnection _Connection;
        /// <summary></summary>
        private ConnectionState _ConnectionState;
        private StateChangeEventHandler _ConnectionStateChangedHandler;
    }
}
